HelloWorld專案程式碼完成後的執行架構將如下圖所示:
此為Client端和Silo後端各自從程式起動到結束的執行流程,其中最重要的部分是Client端呼叫Silo後端Grain RPC方法的過程:
Connect()
方法取得和Silo後端的TCP/IP連線。GetGrain<T>()
方法取得Grain在Client端的RPC Proxy實例。SayHello()
,此時Grain RPC Proxy實例會將原本C#的非同步呼叫轉換成RPC方法的底層呼叫機制,透過TCP/IP網路傳送呼叫參數給距離最近的Silo後端。雖然圖中是畫成Client和Server端是靠TCP/IP網路連接,但後面示範的程式實際上Client和Server都是跑在同台電腦用Localhost的方式連接:Client端的ClientBuilder程式碼會呼叫UseLocalhostClustering()擴充方法以便稍後呼叫Connect()
時連接本機Silo,而Server端的SiloHostBuilder程式碼也有一個UseLocalhostClustering()擴充方法便配置使用Local的測試用Silo配置。
IHelloGrain.cs
檔案,將Day03設計的RPC介面程式碼貼上:
using System.Threading.Tasks;
using Orleans;
namespace RpcDemo.Interfaces.Hello;
public interface IHelloGrain : IGrainWithIntegerKey
{
Task<string> SayHello(string greeting);
}
HelloGrain.cs
檔案,將Day03設計的Grain實作程式碼貼上:
using System.Threading.Tasks;
using Orleans;
using RpcDemo.Interfaces.Hello;
namespace RpcDemo.Grains.Greeting;
public class HelloGrain : Grain, IHelloGrain
{
public Task<string> SayHello(string greeting)
{
return Task.FromResult($"Hello {greeting}!");
}
}
Program.cs
修改為:
using System.Net;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Configuration;
using Orleans.Hosting;
using RpcDemo.Grains.Greeting;
var siloHost = new SiloHostBuilder()
.UseLocalhostClustering()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "console-host-01";
options.ServiceId = "Demo Greeting Service";
})
.Configure<EndpointOptions>(options => options.AdvertisedIPAddress = IPAddress.Loopback)
.ConfigureApplicationParts(parts =>
{
parts.AddApplicationPart(typeof(HelloGrain).Assembly).WithReferences();
})
.ConfigureLogging(logging =>
{
logging.AddConsole();
logging.AddDebug();
})
.Build();
//Tricks to manually wait for Ctrl+C key press
var waitForProcessShutdown = new ManualResetEvent(false);
Console.CancelKeyPress += (sender, eventArgs) =>
{
eventArgs.Cancel = true;
waitForProcessShutdown.Set();
};
await siloHost.StartAsync();
Console.WriteLine("===\r\nOrleans Silo started and able to connect,\r\nPress Ctrl+C to shutdown when client finish demonstration...\r\n===");
waitForProcessShutdown.WaitOne();
Console.WriteLine("Shutting down Silo...");
await siloHost.StopAsync().ConfigureAwait(false);
Console.WriteLine("===\r\nSilo shutdown complete, exiting...\r\n===");
Environment.Exit(0);
Program.cs
修改為:
using Orleans;
using Orleans.Configuration;
using RpcDemo.Interfaces.Hello;
using static System.Console;
WriteLine("\r\n---Orleans RPCDemo Client---");
WriteLine("\r\n---\r\nInitializing Orleans Client...\r\n---");
var client = new ClientBuilder()
.UseLocalhostClustering()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "console-client-01";
options.ServiceId = "Demo Greeting Service";
})
.ConfigureApplicationParts(parts =>
{
parts.AddApplicationPart(typeof(IHelloGrain).Assembly).WithReferences();
})
.Build();
WriteLine(
"Please wait until Orleans Server is started and ready for connections, then press any key to start connect...");
ReadKey();
await client.Connect();
WriteLine("\r\n---\r\nOrleans Client connected\r\n---");
var helloGrain = client.GetGrain<IHelloGrain>(0);
var helloResult = await helloGrain.SayHello("Orleans");
WriteLine($"\r\n---\r\nCall HelloGrain.SayHello(\"Orleans\") =\r\n{helloResult}\r\n---");
WriteLine("Demonstration finished, press any key to exit...");
ReadKey();
await client.Close();
client.Dispose();
task.json
改為下列內容:
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"dependsOn": [
"build server",
"build client"
],
"dependsOrder": "sequence",
"group": "build"
},
{
"label": "build client",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/Hosting/Client/RpcDemo.Client.Console/RpcDemo.Client.Console.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build server",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/Hosting/Server/RpcDemo.Hosting.Console/RpcDemo.Hosting.Console.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}
launch.json
改為下列內容:
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Server",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build server",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/Hosting/Server/RpcDemo.Hosting.Console/bin/Debug/net6.0/RpcDemo.Hosting.Console.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Hosting/Server/RpcDemo.Hosting.Console",
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": "Launch Client",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build client",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/Hosting/Client/RpcDemo.Client.Console/bin/Debug/net6.0/RpcDemo.Client.Console.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Hosting/Client/RpcDemo.Client.Console",
"console": "externalTerminal",
"stopAtEntry": false
}
]
}
SayHello()
RPC方法的結果:整個完成的範例程式GitHub專案在:
https://github.com/windperson/OrleansRpcDemo/tree/day06
明天將會介紹另一種使用.NET Interactive Notebooks來驗證Grain RPC方法呼叫的機制,比較不需要準備這麼多程式碼專案。